/* See COPYRIGHT for copyright information. */

#include <inc/x86.h>
#include <inc/error.h>
#include <inc/string.h>
#include <inc/assert.h>

#include <kern/env.h>
#include <kern/pmap.h>
#include <kern/trap.h>
#include <kern/syscall.h>
#include <kern/console.h>
#include <kern/sched.h>

// Print a string to the system console.
// The string is exactly 'len' characters long.
// Destroys the environment on memory errors.
static void
sys_cputs(const char *s, size_t len)
{
	// Check that the user has permission to read memory [s, s+len).
	// Destroy the environment if not.

	// LAB 3: Your code here.
	if(curenv->env_tf.tf_cs & 3)
		user_mem_assert(curenv, s, len, PTE_P|PTE_U);

	// Print the string supplied by the user.
	cprintf("%.*s", len, s);
}

// Read a character from the system console without blocking.
// Returns the character, or 0 if there is no input waiting.
static int
sys_cgetc(void)
{
	return cons_getc();
}

// Returns the current environment's envid.
static envid_t
sys_getenvid(void)
{
	return curenv->env_id;
}

// Destroy a given environment (possibly the currently running environment).
//
// Returns 0 on success, < 0 on error.  Errors are:
//	-E_BAD_ENV if environment envid doesn't currently exist,
//		or the caller doesn't have permission to change envid.
static int
sys_env_destroy(envid_t envid)
{
	int r;
	struct Env *e;

	if ((r = envid2env(envid, &e, 1)) < 0)
		return r;
	if (e == curenv)
		cprintf("[%08x] exiting gracefully\n", curenv->env_id);
	else
		cprintf("[%08x] destroying %08x\n", curenv->env_id, e->env_id);
	env_destroy(e);
	return 0;
}

// Deschedule current environment and pick a different one to run.
static void
sys_yield(void)
{
	sched_yield();
}

// Allocate a new environment.
// Returns envid of new environment, or < 0 on error.  Errors are:
//	-E_NO_FREE_ENV if no free environment is available.
//	-E_NO_MEM on memory exhaustion.
static envid_t
sys_exofork(void)
{
	// Create the new environment with env_alloc(), from kern/env.c.
	// It should be left as env_alloc created it, except that
	// status is set to ENV_NOT_RUNNABLE, and the register set is copied
	// from the current environment -- but tweaked so sys_exofork
	// will appear to return 0.

	// LAB 4: Your code here.
	struct Env* newEnv,*currentEnv;
	int32_t flag;
	//首先获取当前环境。
	envid2env(0,&currentEnv,1);
	assert(currentEnv);
	//通过env_alloc()来分配新的空环境，父进程id设置为currentEnv->env_id
	flag=env_alloc(&newEnv,currentEnv->env_id);
	if(flag<0)
	return flag;
	//设置属性。继承父进程。
	newEnv->env_tf=currentEnv->env_tf;
	newEnv->env_status=ENV_NOT_RUNNABLE;
	newEnv->env_tf.tf_regs.reg_eax = 0;
	return newEnv->env_id;
}

// Set envid's env_status to status, which must be ENV_RUNNABLE
// or ENV_NOT_RUNNABLE.
//
// Returns 0 on success, < 0 on error.  Errors are:
//	-E_BAD_ENV if environment envid doesn't currently exist,
//		or the caller doesn't have permission to change envid.
//	-E_INVAL if status is not a valid status for an environment.
static int
sys_env_set_status(envid_t envid, int status)
{
	// Hint: Use the 'envid2env' function from kern/env.c to translate an
	// envid to a struct Env.
	// You should set envid2env's third argument to 1, which will
	// check whether the current environment has permission to set
	// envid's status.

	// LAB 4: Your code here.
	struct Env *theEnv;
	if(envid2env(envid, &theEnv, 1) < 0)return -E_BAD_ENV;	//注意这的最后一个参数checkperm要设置为1，检查修改这个env的进程是要么是这个env的父进程，要么就是env自己。不然不允许随便进行状态修改。
	if(status != ENV_RUNNABLE && status != ENV_NOT_RUNNABLE)return -E_INVAL;
	theEnv->env_status = status;
	return 0;
}

// Set the page fault upcall for 'envid' by modifying the corresponding struct
// Env's 'env_pgfault_upcall' field.  When 'envid' causes a page fault, the
// kernel will push a fault record onto the exception stack, then branch to
// 'func'.
//
// Returns 0 on success, < 0 on error.  Errors are:
//	-E_BAD_ENV if environment envid doesn't currently exist,
//		or the caller doesn't have permission to change envid.
// Set envid's trap frame to 'tf'.
// tf is modified to make sure that user environments always run at code
// protection level 3 (CPL 3), interrupts enabled, and IOPL of 0.
//
// Returns 0 on success, < 0 on error.  Errors are:
//	-E_BAD_ENV if environment envid doesn't currently exist,
//		or the caller doesn't have permission to change envid.
static int
sys_env_set_trapframe(envid_t envid, struct Trapframe *tf)
{
	// LAB 5: Your code here.
	// Remember to check whether the user has supplied us with a good
	// address!
	struct Env *e;
	int32_t ret;
	if ((ret = envid2env(envid, &e, 1)) < 0) {
		return ret; // -E_BAD_ENV
	}
	if ((ret = user_mem_check(e, tf, sizeof(struct Trapframe), PTE_U)) < 0) {
		return ret;
	}
	
	memmove(&e->env_tf, tf, sizeof(struct Trapframe));
	e->env_tf.tf_ds = GD_UD | 3;
	e->env_tf.tf_es = GD_UD | 3;
	e->env_tf.tf_ss = GD_UD | 3;
	e->env_tf.tf_cs = GD_UT | 3;
	e->env_tf.tf_eflags |= FL_IF;
	e->env_tf.tf_eflags &= ~FL_IOPL_MASK; //普通进程不能有IO权限
	return 0;
}


static int
sys_env_set_pgfault_upcall(envid_t envid, void *func)
{
	// LAB 4: Your code here.
	struct Env*env;
	//转为对应Env
	int ret=envid2env(envid,&env,1);
	if(ret<0){
		return -E_BAD_ENV;
	}
	//绑定函数。
	env->env_pgfault_upcall=func;
	return 0;
}

// Allocate a page of memory and map it at 'va' with permission
// 'perm' in the address space of 'envid'.
// The page's contents are set to 0.
// If a page is already mapped at 'va', that page is unmapped as a
// side effect.
//
// perm -- PTE_U | PTE_P must be set, PTE_AVAIL | PTE_W may or may not be set,
//         but no other bits may be set.  See PTE_SYSCALL in inc/mmu.h.
//
// Return 0 on success, < 0 on error.  Errors are:
//	-E_BAD_ENV if environment envid doesn't currently exist,
//		or the caller doesn't have permission to change envid.
//	-E_INVAL if va >= UTOP, or va is not page-aligned.
//	-E_INVAL if perm is inappropriate (see above).
//	-E_NO_MEM if there's no memory to allocate the new page,
//		or to allocate any necessary page tables.
static int
sys_page_alloc(envid_t envid, void *va, int perm)
{
	// Hint: This function is a wrapper around page_alloc() and
	//   page_insert() from kern/pmap.c.
	//   Most of the new code you write should be to check the
	//   parameters for correctness.
	//   If page_insert() fails, remember to free the page you
	//   allocated!

	// LAB 4: Your code here.
	struct Env *theEnv;
	struct PageInfo *pageInfo;
	int32_t leastPerm = (PTE_U | PTE_P);
	if(envid2env(envid, &theEnv, 1) < 0)return -E_BAD_ENV;
	if((uintptr_t)va >= UTOP || ((uintptr_t)va & (PGSIZE - 1)))return -E_INVAL;
	if((perm & leastPerm) != leastPerm || (perm & (~PTE_SYSCALL)))return -E_INVAL; 
	if(!(pageInfo = page_alloc(ALLOC_ZERO)))return -E_NO_MEM;
	if(page_insert(theEnv->env_pgdir, pageInfo, va, perm) < 0){
		page_free(pageInfo);
		return -E_NO_MEM;
	}
	return 0;
}

// Map the page of memory at 'srcva' in srcenvid's address space
// at 'dstva' in dstenvid's address space with permission 'perm'.
// Perm has the same restrictions as in sys_page_alloc, except
// that it also must not grant write access to a read-only
// page.
//
// Return 0 on success, < 0 on error.  Errors are:
//	-E_BAD_ENV if srcenvid and/or dstenvid doesn't currently exist,
//		or the caller doesn't have permission to change one of them.
//	-E_INVAL if srcva >= UTOP or srcva is not page-aligned,
//		or dstva >= UTOP or dstva is not page-aligned.
//	-E_INVAL is srcva is not mapped in srcenvid's address space.
//	-E_INVAL if perm is inappropriate (see sys_page_alloc).
//	-E_INVAL if (perm & PTE_W), but srcva is read-only in srcenvid's
//		address space.
//	-E_NO_MEM if there's no memory to allocate any necessary page tables.
static int
sys_page_map(envid_t srcenvid, void *srcva,
	     envid_t dstenvid, void *dstva, int perm)
{
	// Hint: This function is a wrapper around page_lookup() and
	//   page_insert() from kern/pmap.c.
	//   Again, most of the new code you write should be to check the
	//   parameters for correctness.
	//   Use the third argument to page_lookup() to
	//   check the current permissions on the page.

	// LAB 4: Your code here.
	pte_t *srcPte;
	int32_t leastPerm = (PTE_U | PTE_P);
	struct PageInfo *srcPageInfo;
	struct Env *srcEnv, *dstEnv;
	if(envid2env(srcenvid, &srcEnv, 1) < 0 || envid2env(dstenvid, &dstEnv, 1) < 0)return -E_BAD_ENV;
	if((uintptr_t)srcva >= UTOP || (uintptr_t)dstva >= UTOP || ((uintptr_t)srcva & (PGSIZE - 1)) || ((uintptr_t)dstva & (PGSIZE - 1)))return -E_INVAL;
	if((perm & leastPerm) != leastPerm || (perm & (~PTE_SYSCALL)))return -E_INVAL; // PTE_SYSCALL == PTE_U | PTE_P | PTE_AVAIL | PTE_W
	if(!(srcPageInfo = page_lookup(srcEnv->env_pgdir, srcva, &srcPte)))return -E_INVAL;
    //通过page_lookup查找对应的物理页srcPageInfo，通过page2pa()宏可以确定物理页地址
	if((perm & PTE_W) && !((*srcPte) & PTE_W))return -E_INVAL;
    //因此最终调用page_insert，将物理页srcPageInfo，映射到页表dstEnv->env_pgdir下的dstva地址处。
	return page_insert(dstEnv->env_pgdir, srcPageInfo, dstva, perm);
}

// Unmap the page of memory at 'va' in the address space of 'envid'.
// If no page is mapped, the function silently succeeds.
//
// Return 0 on success, < 0 on error.  Errors are:
//	-E_BAD_ENV if environment envid doesn't currently exist,
//		or the caller doesn't have permission to change envid.
//	-E_INVAL if va >= UTOP, or va is not page-aligned.
static int
sys_page_unmap(envid_t envid, void *va)
{
	// Hint: This function is a wrapper around page_remove().

	// LAB 4: Your code here.
	struct Env *theEnv;
	if(envid2env(envid, &theEnv, 1) < 0)return -E_BAD_ENV;
	if((uintptr_t)va >= UTOP || (uintptr_t)va & (PGSIZE - 1))return -E_INVAL;
	page_remove(theEnv->env_pgdir, va);
	return 0;
}

// Try to send 'value' to the target env 'envid'.
// If srcva < UTOP, then also send page currently mapped at 'srcva',
// so that receiver gets a duplicate mapping of the same page.
//
// The send fails with a return value of -E_IPC_NOT_RECV if the
// target is not blocked, waiting for an IPC.
//
// The send also can fail for the other reasons listed below.
//
// Otherwise, the send succeeds, and the target's ipc fields are
// updated as follows:
//    env_ipc_recving is set to 0 to block future sends;
//    env_ipc_from is set to the sending envid;
//    env_ipc_value is set to the 'value' parameter;
//    env_ipc_perm is set to 'perm' if a page was transferred, 0 otherwise.
// The target environment is marked runnable again, returning 0
// from the paused sys_ipc_recv system call.  (Hint: does the
// sys_ipc_recv function ever actually return?)
//
// If the sender wants to send a page but the receiver isn't asking for one,
// then no page mapping is transferred, but no error occurs.
// The ipc only happens when no errors occur.
//
// Returns 0 on success, < 0 on error.
// Errors are:
//	-E_BAD_ENV if environment envid doesn't currently exist.
//		(No need to check permissions.)
//	-E_IPC_NOT_RECV if envid is not currently blocked in sys_ipc_recv,
//		or another environment managed to send first.
//	-E_INVAL if srcva < UTOP but srcva is not page-aligned.
//	-E_INVAL if srcva < UTOP and perm is inappropriate
//		(see sys_page_alloc).
//	-E_INVAL if srcva < UTOP but srcva is not mapped in the caller's
//		address space.
//	-E_INVAL if (perm & PTE_W), but srcva is read-only in the
//		current environment's address space.
//	-E_NO_MEM if there's not enough memory to map srcva in envid's
//		address space.
static int
sys_ipc_try_send(envid_t envid, uint32_t value, void *srcva, unsigned perm)
{
	// LAB 4: Your code here.
	// LAB 4: Your code here.
	struct Env *receiveEnv,*currentEnv;
	int32_t error, leastPerm = (PTE_U|PTE_P);
    // obtain current environment.
	envid2env(0,&currentEnv,0);
	assert(currentEnv);
    // Environment envid doesn't currently exist.
	if((error = envid2env(envid,&receiveEnv,0))<0)
		return error;	//bad env
	//接下来是一系列的检查。
    // Envid is not currently blocked in sys_ipc_recv or another enviironment managed to send first.【标识现在接收方等待接收】
	if(!receiveEnv->env_ipc_recving)	
		return -E_IPC_NOT_RECV;
   	// ipc通讯是通过映射的方式来做的。
	if((uintptr_t)srcva<UTOP){
		struct PageInfo *pageinfo;
		pte_t *pte;
        // 检查要发送过去的页是否对齐。
		if((uintptr_t)srcva&(PGSIZE-1))
			return -E_INVAL;
        // 检查是否具备最低权限。
		if((uint32_t)(leastPerm&perm)!=leastPerm || (perm&(~PTE_SYSCALL)))
			return -E_INVAL;
        // 找到对应的物理页。
		if(!(pageinfo = page_lookup(currentEnv->env_pgdir,srcva,&pte)))
			return -E_INVAL;
        // 再度检查权限。
		if((perm&PTE_W)&&!((*pte)&PTE_W))
			return -E_INVAL;
		if ((uintptr_t)receiveEnv->env_ipc_dstva < UTOP)
		{   //给另一个进程插入对应的物理页。
			if((error = page_insert(receiveEnv->env_pgdir,pageinfo,receiveEnv->env_ipc_dstva,perm))<0)
				return error;
		}		
	}
    // restore status.
	receiveEnv->env_ipc_recving = 0;
	receiveEnv->env_ipc_from = currentEnv->env_id;
	receiveEnv->env_ipc_value = value;
	receiveEnv->env_ipc_perm = perm;
	// Then we should mark the receiver environment as ENV_RUNNABLE.
	receiveEnv->env_status = ENV_RUNNABLE;
	// Return 0 from the paused sys_env_recv system call.
	// Howerver,the corresponding sys_ipc_recv function will never return,so we need to modify receiver's environment's trapframe.
    receiveEnv->env_tf.tf_regs.reg_eax = 0;
    // sys_ipc_try_send could return 0.
	return 0;
}

// Block until a value is ready.  Record that you want to receive
// using the env_ipc_recving and env_ipc_dstva fields of struct Env,
// mark yourself not runnable, and then give up the CPU.
//
// If 'dstva' is < UTOP, then you are willing to receive a page of data.
// 'dstva' is the virtual address at which the sent page should be mapped.
//
// This function only returns on error, but the system call will eventually
// return 0 on success.
// Return < 0 on error.  Errors are:
//	-E_INVAL if dstva < UTOP but dstva is not page-aligned.
static int
sys_ipc_recv(void *dstva)
{
	// LAB 4: Your code here.
	//话说回来，ipc_try_send直接插了一页给接收进程，这玩意还要怎么写呢？
	if((uintptr_t)dstva < UTOP && (uintptr_t)dstva & (PGSIZE - 1))return -E_INVAL;
	// Willing wo receive a page of data.
	struct Env *currentEnv;
	envid2env(0, &currentEnv, 0);
	assert(currentEnv);

	currentEnv->env_status = ENV_NOT_RUNNABLE;
	currentEnv->env_ipc_recving = 1;
	//直接改变接收的虚拟页，方便下次接收。
	currentEnv->env_ipc_dstva = dstva;
    //give up CPU，调度别的进程执行
	sys_yield();
    //因此之后控制权交由别的进程，这个进程不会从这里返回。直到下次被调度，直接从tf中获取eip回到用户态执行【返回值从tf->reg_eax中得到，其值在send中设置为0】
	return 0; // eliminate compile error.
}

// Dispatches to the correct kernel function, passing the arguments.
int32_t
syscall(uint32_t syscallno, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5)
{
	// Call the function corresponding to the 'syscallno' parameter.
	// Return any appropriate return value.
	// LAB 3: Your code here.

	
	int32_t ret;

	switch (syscallno) {
		case SYS_cputs:
			sys_cputs((char *)a1, (size_t)a2);
			return 0;
			break;
		case SYS_cgetc:
			return sys_cgetc();
			break;
			
		case SYS_getenvid:
			return sys_getenvid();
			break;
			
		case SYS_env_destroy:
			return sys_env_destroy((envid_t)a1);
			break;
		case SYS_yield:
		    sys_yield();
			return 0;
			break;
		case SYS_page_alloc:
        return sys_page_alloc((envid_t)a1, (void * )a2, (int )a3);
        break;
        case SYS_page_map:
        return sys_page_map((envid_t) a1, (void *) a2, (envid_t) a3, (void *) a4, (int) a5);
        break;
        case SYS_page_unmap:
        return sys_page_unmap((envid_t) a1, (void *) a2);
        break;
        case SYS_exofork:
        return sys_exofork();
        break;
	    case SYS_env_set_status:
		return sys_env_set_status((envid_t) a1, (int) a2);
		break;
		case SYS_env_set_pgfault_upcall:
		return sys_env_set_pgfault_upcall((envid_t)a1,(void*)a2);
		break;
		case SYS_ipc_try_send:
		return sys_ipc_try_send((envid_t)a1,(uint32_t)a2,(void*)a3,(unsigned int)a4);
		break;
	    case SYS_ipc_recv:
		return sys_ipc_recv((void *)a1);
		break;
		case SYS_env_set_trapframe:
		return sys_env_set_trapframe((envid_t)a1,(struct Trapframe*)a2);
		break;
			
	    default:
		return -E_INVAL;
	}
	
}

